@陈逸云 吴怡:《古籍文献中人名实体信息的提取方法及实现研究》

摘要

引言

一、 提取人名信息的若干难题

二、 解决方案:分类词典树Trie分词 + FAISS消歧

三、 借助实体提取技术构建人物本体库

四、 应用开发

总结

什么是 faiss

好的,当然可以。

FAISS (Facebook AI Similarity Search) 向量相似度检索的实现,本质上是为了解决一个核心问题:如何在海量(百万、十亿甚至更多)的高维向量中,快速找到与查询向量最相似的几个向量。

如果我们用“暴力搜索”(Brute-force Search)的方式,即把查询向量和数据库里的每一个向量都计算一次距离,那么当数据量巨大时,这个过程会慢得无法接受。FAISS 的精髓在于它使用了一系列**近似最近邻(Approximate Nearest Neighbor, ANN)**的算法,通过牺牲极小的精度来换取成百上千倍的检索速度。

下面,我将结合之前笔记中的“人名识别”案例,用一个通俗的比喻来分步解释其实现原理。

想象一下,你要在一个拥有一亿本藏书的巨大图书馆里,为你手上的一本书(查询向量)找到内容最相似的几本书(相似向量)。

核心前提:向量嵌入(Vector Embedding)

在检索之前,我们需要将所有信息都“翻译”成向量(一长串数字)。

  1. 数据库向量:对于人物库中的每一个人物(比如历史上的白居易),系统会根据他的各种属性(姓名“白居易”、字“乐天”、朝代“唐朝”、官职“左拾遗”、密友“元稹”、籍贯等)生成一个唯一的向量。这个向量在多维空间中的位置,就代表了“白居易”这个人物的“语义”。
  2. 查询向量:当系统在古籍中读到“乐天居士”这个词时,它也会根据这个词本身以及它的上下文(例如,这篇文章的作者是谁、创作于哪个朝代、附近是否提到了“元稹”或“江州司马”等)生成一个临时的查询向量。

目标:在代表了一亿个人物信息的向量库中,快速找到与“乐天居士”这个查询向量距离最近的那个向量,从而判断“乐天居士”指的就是“白居易”。


FAISS 的两步核心实现策略

FAISS 主要通过 “聚类”“量化” 两大技术来加速检索。

第 1 步:聚类(Clustering) - 建立索引,缩小搜索范围

暴力搜索相当于在一亿本书中一本一本地翻。FAISS 的第一步是先给图书馆分区和编目

第 2 步:量化(Quantization) - 压缩数据,加速计算

即使范围缩小到了几万,每个向量本身可能依然很大(比如由 128 或 256 个浮点数组成),占用大量内存,且逐一比较仍然耗时。FAISS 的第二步是给每个分区里的书进行“内容摘要”

总结:FAISS 的完整工作流

结合这两步,FAISS 最常用的索引类型 IVFADC (Inverted File system with Asymmetric Distance Computation) 的工作流程如下:

  1. 离线建库

    • 对所有人物的向量进行 聚类(IVF),建立分区。
    • 对每个向量进行 量化(PQ),将其压缩并存入相应的分区。
  2. 在线查询 (当遇到“乐天居士”时):

    • 拿到“乐天居士”的原始查询向量
    • 第一步筛选:将查询向量与所有分区的中心点比较,快速定位到最相关的几个分区。
    • 第二步筛选:在选定的分区内,将原始查询向量与分区内所有被压缩过的向量进行快速距离估算。
    • 返回结果:从估算结果中,返回距离最近的 Top-K 个向量的ID(比如,“白居易”的ID)。

通过**聚类(缩小搜索空间)量化(降低存储和计算成本)**的巧妙结合,FAISS 实现了在牺牲极小(通常可忽略不计)精度的情况下,对海量高维向量进行超高速的相似度检索,这使其成为解决类似古籍人名消歧等大规模实体链接问题的理想工具。

词向量从哪来?

这是一个非常棒的问题!我们已经知道了FAISS是向量的“搜索引擎”,那么这些能代表词语和实体意义的向量,究竟是如何被“创造”出来的呢?

这个过程被称为词嵌入(Word Embedding),它是现代自然语言处理(NLP)的基石。其核心思想源于一句名言:

"You shall know a word by the company it keeps." (你将通过一个词的上下文来了解它。)

简单来说,如果两个词经常出现在相似的语境中,那么它们的向量在多维空间中也应该彼此靠近。例如,在大量文本中,“猫”和“狗”都经常与“宠物”、“食物”、“奔跑”等词一起出现,所以它们的向量会很相似。

下面我们来看看生成这些向量的主流方法,从经典模型到现代模型。

1. 经典静态词向量模型 (Static Word Embeddings)

这类模型的特点是:一个词只有一个固定的向量,无论它在哪个句子中。

a) Word2Vec (Google, 2013)

这是最经典、最广为人知的模型。它通过一个简单的神经网络,在海量文本上进行“预测任务”的训练,而我们真正想要的,是训练过程中产生的“副产品”——词向量。它主要有两种训练方式:

  1. CBOW (Continuous Bag-of-Words) - 根据上下文猜中间的词

    • 任务:把一个词的上下文(比如前后各两个词)作为输入,预测这个中心词是什么。
    • 比喻:做“完形填空”。比如,看到句子 “一只___正在追老鼠”,模型需要猜出中间的词很可能是“猫”。
    • 过程:为了完成这个任务,模型必须学会哪些词的上下文是相似的。在这个过程中,它就为每个词学习到了一个能代表其“角色”和“意义”的向量。
  2. Skip-gram - 根据中间的词猜上下文

    • 任务:与CBOW相反,输入一个中心词,预测它周围的上下文可能出现哪些词。
    • 比喻:给出“猫”,模型需要猜出它周围可能出现“一只”、“追”、“老鼠”、“宠物”等词。
    • 效果:在大型语料库上,Skip-gram通常能学到比CBOW更高质量的词向量。

b) GloVe (Global Vectors for Word Representation) (Stanford, 2014)

Word2Vec利用的是局部上下文信息(滑动窗口),而GloVe则试图结合全局的统计信息

2. 现代动态/上下文词向量模型 (Contextual Word Embeddings)

经典模型的最大缺点是“一词一义”。比如“bank”在“river bank”(河岸)和“investment bank”(投资银行)中,会得到完全相同的向量,这显然是不对的。现代模型解决了这个问题。

BERT (Bidirectional Encoder Representations from Transformers) (Google, 2018)

BERT是革命性的模型,它开启了NLP的新时代。

应用到古籍人名识别案例

回到我们最初的论文案例,系统是如何为“白居易”和在文中读到的“乐天居士”生成向量的呢?

  1. 语料库:首先,研究人员需要一个巨大的古籍语料库(比如《四库全书》、全唐诗、全宋词等)。
  2. 模型训练:他们会使用像BERT这样的模型,在这个古汉语语料库上进行预训练。这使得模型能够深刻理解古文的语法、词汇和知识。
  3. 生成实体向量
    • 对于人物库中的“白居易”:系统会将其所有信息(姓名、字号、官职、好友元稹、相关作品等)构成一个描述性文本,输入到训练好的BERT模型中,从而为“白居易”这个实体生成一个高质量的、聚合了所有信息的实体向量 (Entity Vector)
    • 对于文本中读到的“乐天居士”:系统会将包含“乐天居士”的整个句子或段落输入BERT,BERT会动态地为“乐天居士”这个词组生成一个查询向量 (Query Vector)。这个向量已经编码了它的上下文信息(比如,如果上文提到了“元稹”,这个向量就会和“元稹”的向量更接近)。
  4. 相似度检索:最后,将这个查询向量扔进FAISS,去和人物库中成千上万个实体向量进行比对,最终以极高的概率匹配到“白居易”的实体向量。

总结一下:词向量是通过让模型在海量文本中学习“词语的上下文用法”而产生的数学表示。经典模型(如Word2Vec)为每个词生成一个静态向量,而现代模型(如BERT)则能根据具体语境,为同一个词生成不同的动态向量,从而极大地提升了语言理解的准确性。

什么是分类词典树Trie分词

当然可以!这是一个非常核心的概念。我们来把它拆解成三个部分:分词词典分类词典树Trie,一步步解释清楚。


1. 什么是“分词” (Segmentation)?

对于英文这样的语言,单词之间有天然的空格作为分隔符,计算机处理起来很方便。

This is a sentence. -> ["This", "is", "a", "sentence."]

但对于中文,句子是连续的字符流,没有明确的边界。

中国历代人物传记资料库

计算机不知道是该切分成 ["中国", "历代", "人物", "传记", "资料库"] 还是 ["中国", "历代", "人物", "传", "记", "资料", "库"]

分词,就是通过算法将连续的汉字序列切分成一个个具有独立意义的词语的过程。这是所有中文自然语言处理任务的第一步,也是最基础的一步。

2. 什么是“基于词典”的分词?

实现分词有很多方法,最直观的一种就是基于词典的分词

这种简单方法的缺点显而易见:速度慢(每次都要在巨大的词典里查找),且容易出错(“贪心”地选择了最长的词,导致错误)。为了解决这个问题,就需要更高效的数据结构。

3. 什么是“分类词典树Trie”?

这就是论文中提到的解决方案。它不仅高效,而且更智能。

a) 什么是 Trie 树(字典树)?

Trie 树是一种专门为字符串查找而设计的树形数据结构。它非常适合用来实现一个高效的“词典”。

我们来构建一个简单的 Trie 树:
假设我们的词典里有三个词:{ “白居易”, “白傅”, “元稹” }

  1. 插入“白居易”:

    • Root -> 白 -> 居 -> 易 (在“易”节点上做一个标记,表示“白居易”是一个完整的词)
  2. 插入“白傅”:

    • 路径 Root -> 白 已经存在了,不用新建。
    • 从“白”节点出发,新建路径 -> 傅 (在“傅”节点上做一个标记,表示“白傅”是一个完整的词)
  3. 插入“元稹”:

    • Root 出发,新建路径 -> 元 -> 稹 (在“稹”节点上做一个标记)

构建出的 Trie 树大致如下:

      (Root)
      /      \
     白 ------ 元
    /  \        \
   居   傅(词)    稹(词)
  /
 易(词)

使用 Trie 树分词的优势:

b) 什么是“分类 (Classified)”?

这是论文中方法的精髓,它让 Trie 树变得更加强大。研究人员不仅仅是把词语放进树里,还给它们贴上了类别标签

词典不再是单一的,而是分成了好几类:

在构建 Trie 树时,当一个词结束时,不仅要做标记,还要把它的类别信息也存放在那个节点上。

      (Root)
      /      \
     白 ------ 元
    /  \        \
   居   傅(词, [人名])  稹(词, [人名])
  /
 易(词, [人名])

注意:一个词可以有多个类别标签,例如“兰陵王”既是[人名],也是[词牌名]。

总结:分类词典树Trie分词的工作流程

现在,我们把所有部分组合起来,看看它是如何工作的。

输入句子: “白傅在长安作诗”

  1. 扫描与匹配:程序从第一个字“白”开始,在分类 Trie 树中向下遍历。
  2. 识别候选与分类
    • 当走到路径 Root -> 白 -> 傅 时,程序发现“傅”节点被标记为 词尾,并且类别是 [人名]
    • 于是,它识别出一个候选词 “白傅”,并立刻知道它是一个 人名
  3. 继续扫描
    • 程序继续从“在”字开始扫描,匹配到普通词“在”
    • 接着从“长”开始,匹配到路径 Root -> 长 -> 安,发现“安”节点被标记为 词尾,类别是 [地名]
    • 于是,识别出候选词 “长安”,并知道它是一个 地名
  4. 输出与交接
    • 分词步骤的最终输出不再是简单的词语列表,而是 带有类别信息的结构化结果
      { 词: "白傅", 类别: "人名" }
      { 词: "在", 类别: "普通词" }
      { 词: "长安", 类别: "地名" }
      { 词: "作诗", 类别: "普通词" }
  5. 启动下一步:当系统识别出一个类别为 [人名] 的词(如“白傅”)后,它就会把这个词和它的上下文信息,移交给下一个处理模块——也就是我们之前讨论的 FAISS 向量相似度检索系统,去完成最终的实体链接任务(即判断这个“白傅”具体是指哪个人——白居易)。

简而言之,分类词典树Trie分词是一种“一石二鸟”的高效策略:它在快速完成分词的同时,也完成了对词语的初步实体分类,为后续更复杂的AI任务(如实体链接)做好了数据准备,形成了一条清晰、高效的处理流水线。